﻿/* =============================================
API 中包含以下内容

  Class
    QZData
  Function
    GetFileTypeEach(afiles, aDot="")
    QZ_EnvGet(var)
    QZ_GetEnv(aString)
    QZ_GetFileType(aFiles,aDot="")
    QZ_GetTextType(aText)
    QZ_GetWinType(aWinClass, aWinExe)
    QZ_InitClip(byRef aData)
    QZ_Needle(aString)
    QZ_WM_COPYDATA(wParam, lParam)
    QZ_filter(aSource, aNeedle="")
  Label
    QZ_Command_Load:  这个Label不需要调用

*/
return ;避免 Label: QZ_Command_Load 被调用

; QZ 获取的内容按以下格式保存，方便调用 
; 请了解
Class QZData
{
    __new()
    {
        this.x := 0         ; x坐标
        this.y := 0         ; y坐标
        this.hWnd := 0      ; 程序的 ahk_id
        this.control := ""  ; 程序焦点所在控件的 classNN
        this.WinClass:= ""  ; 程序的 ahk_class
        this.WinExe:= ""    ; 程序的 exe名，如notepad.exe
        this.files := ""    ; 获取到的文件列表
        this.text := ""     ; 获取到的文本内容
        this.FilterMode := ""
        this.ClipSave := "" ; 保存剪切板数据
    }
}
; 初始化，获取一次当前进程的数据，再获取剪切板的数据。
; 返回一个 QZData 数据
QZ_Init()
{
    ObjData := QZ_InitWinOnly()
    WinClass := ObjData.WinClass 
    if IsFunc(WinClass "_LoadData") ; 如果有预定义的函数。
        %WinClass%_LoadData(ObjData)
    else
        QZ_InitClip(ObjData) ; 获取剪切板
    return ObjData
}
; 初始化，但只获取当前进程的数据
; 返回一个 QZData 数据
QZ_InitWinOnly()
{
    ObjData := new QZData
    MouseGetPos, posx, posy, id, ctrl ;获取相关的数据 
    WinGet, exe,  ProcessName, ahk_id %id%
    WinGetClass, class, ahk_id %id%
    ObjData.x := posx
    ObjData.y := posy
    ObjData.hwnd := id
    ObjData.WinClass := class
    ObjData.WinExe:= exe
    ObjData.control := ctrl
    return ObjData
}
; 获取剪切板数据，被QZ_Init调用。返回错误代码的信息
; 如果需要单独调用，请先实例化一个QZData对象
; 如：obj := new QZData
;     QZ_InitClip(obj)
;     Msgbox % obj.files

QZ_InitClip(byRef aData)
{
    Global g_qzConfig, QuickZ_ClipSave, QuickZ_Clip
    method  := g_qzConfig.Setting.method
    timeout := g_qzConfig.Setting.timeout
    if not timeout
        timeout := 0.4
    ; 监听剪切板
    CB := ClipboardAll
    aData.ClipBackup := CB
    Clipboard := ""
    if( method = 1 )
      SendInput, ^{Ins}
    else
      SendInput, ^c ;^{vk43sc02E} ;ctrl+c
    ClipWait,% timeout, 1
    ErrorCode := !Errorlevel ; 超时
    if DllCall( "IsClipboardFormatAvailable", "UInt", iFmt:=15)
        aData.files := Clipboard
    else
        aData.text := Clipboard
    QuickZ_Clip := Clipboard
    QuickZ_ClipSave := ClipboardAll
    Clipboard := CB
    return ErrorCode ; 获取到数据返回True、超时返回False
}

/*
doc   包含单个doc类型。 ok
-doc  排除单个doc文件(!doc)。 ok
不含文件夹的文件 ： -folder(!folder)
multifiles 多文件
仅文件夹： folder
仅多文件：-multifiles 
多文件，并且不含doc和folder, multifiles-doc-folder   (multifiles!doc!folder)
多文件，并且全部是doc并且，multifiles+doc (multifiles&doc)
多文件，并且全部是doc和folder，multifiles+doc+folder (multifiles&doc&folder)
多文件或者doc或者非xls, multifiles;doc;-xls (multifiles|doc|xls)
*/
; aNeedle = .doc/.xls/multifiles/folder

QZ_Filter(aSource, aNeedle="", pFileList="")
{
    ObjBack := {}, Index := 1
    if IsObject(aNeedle) ; 文本筛选器(多个类型)
    {
        Loop % aNeedle.MaxIndex()
        {
            NeeldeStr := QZ_Needle(aNeedle[A_Index])
            NeeldeStrNot := QZ_Needle("-" aNeedle[A_Index]) 
            Loop, % aSource.MaxIndex()
            {
                if RegExMatch(aSource[A_Index].filter, NeeldeStrNot) ; 筛选器中带-，马上排除
                    Continue
                if RegExMatch(aSource[A_Index].filter, NeeldeStr) ; 找到匹配的内容读取
                {
                    for i, Item in aSource[A_Index].SubItem
                    {
                        ObjBack[Index] := Item
                        Index++
                    }
                }
            }
        }
    }
    else
    {
        NeeldeStr := QZ_Needle(aNeedle)
        NeeldeStrNot := QZ_Needle("-" aNeedle) ; 满足 -doc; 格式的判断
    }
    Loop, % aSource.MaxIndex()
    {
        if RegExMatch(aSource[A_Index].filter , "i)MultiFiles[-\+]") ; 多文件
           and Regexmatch(aNeedle, "i)^MultiFiles$")
        {
            idx := A_Index, P := 1, ObjType := {}, boolType := false, ToRead := True, strFilter := aSource[idx].filter ";"
            Loop
            {
                P := RegExMatch(strFilter, "i)MultiFiles[-\+][^`;]*(?=`;)", strMatch, P) 
                if not p
                    break
                p += Strlen(strMatch)
                n := 1, m := 1, strMatch .= "-"
                Loop
                {
                    n := RegExMatch(strMatch, "\+[^-\+]*(?=[-\+])", strIn, n)
                    m := RegExMatch(strMatch, "-[^-\+]*(?=[-\+])",  strOut, m)
                    if strlen(strIn)
                    {
                        ObjType[SubStr(strIn,2)] := True
                        boolType := True
                    }
                    if (not n) and (not m)
                        break
                    n += strlen(strIn)
                    m += strlen(strOut)
                    if pFileList[SubStr(strOut,2)] 
                        ToRead := False
                }
            }
            ;/*
            if boolType
            {
                for strType, numCount in pFileList
                {
                    if not strlen(ObjType[strType])
                        ToRead := False
                }
            }
            ;*/
            if ToRead
            {
                for i, Item in aSource[Idx].SubItem
                {
                    ObjBack[Index] := Item
                    Index++
                }
            }
        }
        else
        {
            if RegExMatch(aSource[A_index].filter "`;" , NeeldeStrNot) ; 筛选器中带-，马上排除
                Continue
            if RegExMatch(aSource[A_Index].filter "`;", NeeldeStr) ; 找到匹配的内容读取
            {
                for i, Item in aSource[A_Index].SubItem
                {
                    ObjBack[Index] := Item
                    Index++
                }
            }
        }
    }
    return ObjBack
}

; 返回aText 的文本类型。
; 这里的文本类型是由用户在配置文件里定义的，在文本筛选器里可以设置

QZ_GetTextType(aText)
{
    global g_qzConfig
    ObjMatch := {}, numMatch := 0
    Loop % g_qzConfig.text_regex.MaxIndex()
    {
        text_regex := g_qzConfig.text_regex[A_Index]
        Regex := text_regex.Regex
        if RegExMatch(aText, Regex)
        {
            numMatch++
            ObjMatch[numMatch] := text_regex.name
        }
    }
    if numMatch = 1
        return ObjMatch[1]
    else
        return ObjMatch
    return "AnyText"
}
; 返回aWinClass对应的程序文件名。
; 有些情况下，程序的AHK_class是动态的。这个时候只能通过程序文件名来判断
; 如 g_qzConfig.class 里有一个元素的filter是，vim;notepad.exe
; 那需要过滤到notepad.exe，则需要用户在配置文件的 g_qzConfig.win_comment中有相关定义。这样可以过滤到对应的菜单项用于显示。

QZ_GetWinType(aWinClass, aWinExe)
{
    global g_qzConfig
    Loop % g_qzConfig.win_comment.MaxIndex() ; 遍历配置中的 win_comment 段
    {
        Class_Loopfield := g_qzConfig.win_comment[A_Index].class
        If (aWinClass = Class_Loopfield)   ; 找出与winclass对应的元素
        {
          if strlen(g_qzConfig.win_comment[A_Index].exe) ; 如果有Exe进程的话，返回相应的进程名（如notepad.exe）。否则返回aWinClass
              return g_qzConfig.win_comment[A_Index].exe
        }
    }
    return aWinClass
}

; 返回aFiles对应文件类型, aDot 用于控制返回的是 doc 还是 .doc 或者是 -doc 
; 这里的 . 或者 - 就是取决于 aDot 的值。
; 返回的类似有以下几种：
; MultiFiles 多文件
; Folder 文件夹
; NoExt  无后缀
; Dirve  驱动器，如 C:\
; 或者文件后缀名，如 .ahk .ini .txt

QZ_GetFileType(aFiles,aDot="")
{
    Static cons_MultiFiles := "MultiFiles"
    Static cons_Folder     := "Folder"
    Static cons_NoExt      := "NoExt"
    Static cons_Drive      := "Drive"

    If InStr(aFiles,"`n") ;多文件
    {
        mType := {}
        Loop, Parse, aFiles, `n, `r
        {
            if not strlen(A_loopfield)
                Continue
            ;SplitPath, A_loopfield, , , Ext
            mType[QZ_GetFileType(A_LoopField)] := True
        }
        /*
        mTypeCount := 0
        for i , k in mtype
            mTypeCount++
        if ( mTypeCount = 1 )
            return "#" Ext
        */
        return cons_MultiFiles
    }
    Else If RegExMatch(aFiles,"[a-zA-Z]:\\$") ; 驱动器
    {
        Return cons_Drive
    }
    Else
    {
        Attrib := FileExist(aFiles)
        If InStr(Attrib,"D") ; 文件夹
        {
            Return cons_Folder
        }
        Else
        {
            SplitPath,aFiles,,,ext
            If strlen(ext)
            {
                Return aDot ext ; 后缀名
            }
            Else
            {
                Return cons_NoExt ; 无后缀
            }
        }
    }
}

 ; 获取多文件列表中，每个文件的类型，返回值为数组
QZ_GetFileTypeEach(afiles, aDot="")
{
    Static cons_MultiFiles := "MultiFiles"
    ft := QZ_GetFileType(afiles)
    if ( ft = cons_MultiFiles ) ;or RegExMatch(ft, "^#")
    {
        arr := {}
        Loop, Parse, afiles, `n, `r
        {
            type_loopfield := QZ_GetFileType(A_loopfield, aDot)
            if arr[type_loopfield]
            {
                index := arr[type_loopfield]
                index++
                arr[type_loopfield] := index
            }
            else
            {
                arr[type_loopfield] := 1
            }
        }
        return arr
    }
}
; 获取aString对应配置中的环境变量
QZ_GetEnv(aString)
{
    static Neelde_IsVar := "%[^%]*%"
    static Neelde_MyVar := "i)(^apps$)|(^plugins$)|(^icons$)"
    global g_qzConfig
    p1 := 1
    r := ""
    Loop 
    {
        p2 := RegExMatch(aString, Neelde_IsVar, m, p1)
        If Not p2 
        {
            If P1 > 1
                r .= over
            Else
                r := aString
            Break
        }
        Else If Strlen(m)
        {
            ; 去掉找到的 %var% 两侧的 %
            env := SubStr(m,2,strlen(m)-2)
            if StrLen(g_qzConfig.user_env[env])
                RString := g_qzConfig.user_env[env] ; 配置文件中定义的用户变量
            else if RegExMatch(env, Neelde_MyVar, MyVar)
                RString := A_ScriptDir "\" MyVar ; 预定义的路径
            else if RegExMatch(env,"i)^a_")
                RString := %env% ; AHK内置变量
            else if StrLen(sys := QZ_EnvGet(env))
                RString := sys ; 环境变量
            else
                RString := m 
        }
        inter := SubStr(aString,p1,p2-p1)
        p1 := p2 + Strlen(m)
        over := SubStr(aString,p1)
        r .= inter RString
    }
    return r
}

; QZ_Needle(string)  {{{2
; 把字符串转换为正则匹配式，用于QZ_filter进行过滤
QZ_Needle(aString)
{
    Static neelde_symbol := "\+|\?|\.|\*|\{|\}|\(|\)|\||\^|\$|\[|\]|\\"
    aString := RegExReplace(aString, neelde_symbol, "\$0") ; 转换正则式的关键字符，替换为转义
    match   := RegExReplace(aString ,"\s","\s")  ; 转换空格为 "\s" 的正则式转义，用于 regexmatch
    ;return "i)(^\s*" match "\s*;|^\s*" match "\s*$|;\s*" match "\s*;|(?<![-\+`;])\s*" match ";?\s*$)"
    return "i)(^\s*" match "\s*;|^\s*" match "\s*$|;\s*" match "\s*;|(?<![-\+])\s*" match ";?\s*$)"
}

; 获取系统的环境变量
QZ_EnvGet(var)
{
    EnvGet, back, %Var%
    return back
}

/*
 函数名:  QZ_CheckVaild(pObj, pData)
 说明: 菜单有效性，判断一条选项是否有效，此函数为选用 
 参数: pObj为item.options对象, pData为当前获取到的QZ对象(QZData)
 返回值:  有效返回True，无效返回False
 pObj
 {
     class : {in:"", ex:"", regex:""}
     control : {in:"", ex:"", regex:""}
     filepath := {in:"", ex:"", regex:""}
     if := ""
 }
 */

 QZ_CheckVaild(pObj)
 {
     global ObjData
     Static cons_NotVaild := True
     if not Strlen(pObj.FilterMode) and ObjData.Sep
     {
         return cons_NotVaild
     }
     If strlen(pObj.FilterMode)
     {
         if not QZ_CheckVaild_Sub(1, pObj.FilterMode, QZ_Needle(ObjData.FilterMode), "") 
             return cons_NotVaild
     }
     If Isobject(pObj.class)
     {
         if not QZ_CheckVaild_Sub(pObj.Class.method, pObj.Class.keyword, QZ_Needle(ObjData.winclass), ObjData.winclass)
             return cons_NotVaild
     }
     If Isobject(pObj.control)
     {
         if not QZ_CheckVaild_Sub(pObj.control.method, pObj.control.keyword, QZ_Needle(ObjData.control), ObjData.control)
             return cons_NotVaild
     }
     if ObjData.files
     {
         if IsObject(pObj.type)
         {
            strType := QZ_GetFileType(ObjData.files)
            if not QZ_CheckVaild_Sub(pObj.type.method, pObj.type.keyword, QZ_Needle(strType), strType) and not (strType = "MultiFiles")
                return cons_NotVaild
            /*
            if strlen(pObj.type.keyword)
            {
                FileList := ""
                TypeName := QZ_GetFileType(ObjData.files)
                if (TypeName = "MultiFiles") 
                    FileList := QZ_GetFileTypeEach(ObjData.files) 
                iObj := QZ_filter({1:{filter:pObj.type.keyword,SubItem:{1:"IsType"}}}, TypeName, FileList)
                ;for i, Item in aSource[A_Index].SubItem
                if not Strlen(iObj[1])
                    return cons_NotVaild
            }
            */
         }
         if IsObject(pObj.name)
         {
             iMethod := pObj.name.method
             if iMethod
             {
                 files := ObjData.files
                 strType := QZ_GetFileType(files)
                 if not (strType = "MultiFiles") 
                 {
                     /*
                     Loop, Parse, files, `n, `r
                     {
                         SplitPath, A_LoopField, iFileName
                         break
                     }
                     */
                     keyword := pObj.name.keyword
                     if (iMethod = 3) and not RegExMatch(iFileName, keyword)
                         return cons_NotVaild
                     boldInName := False
                     Loop, Parse, keyword, `;
                     {
                         if (iMethod = 1) and InStr(iFileName, A_LoopField)
                         {
                             boldInName := True
                             break
                         }
                         if (iMethod = 2) and InStr(iFileName, A_LoopField)
                         {
                             return cons_NotVaild
                         }
                     }
                     if (iMethod = 1) and not boldInName 
                         return cons_NotVaild
                 }
             }
         }
         if IsObject(pObj.mtype)
         {
             iMethod := pObj.mType.method
             if iMethod
             {
                 strType := QZ_GetFileTypeEach(ObjData.files)
                 TypeList := ""
                 iKeyword := pObj.mType.keyword
                 for iType, iCnt IN strType
                 {
                     NeeldeType := QZ_Needle(iType)
                     if ( iMethod = 1 ) 
                     {
                         if not Regexmatch(iKeyword ";", NeeldeType)
                             return cons_NotVaild
                     }
                     else if ( iMethod = 2 )
                     {
                         if Regexmatch(iKeyword ";", NeeldeType)
                             return cons_NotVaild
                     }
                     else if ( iMethod = 3 )
                     {
                         if not Regexmatch(iType, iKeyword)
                             return cons_NotVaild
                     }
                 }
             }
         }
         if IsObject(pObj.path)
         {
             iMethod := pObj.path.method
             if iMethod
             {
                 files := ObjData.files
                 Loop, Parse, files, `n, `r
                 {
                     SplitPath, A_LoopField, ,iFileDir
                     break
                 }
                 keyword := pObj.path.keyword
                 if (iMethod = 3) and not RegExMatch(iFileDir, keyword)
                     return cons_NotVaild
                 boldInName := False
                 Loop, Parse, keyword, `;
                 {
                     if (iMethod = 1) and InStr(iFileDir, A_LoopField)
                     {
                         boldInName := True
                         break
                     }
                     if (iMethod = 2) and InStr(iFileDir, A_LoopField)
                     {
                         return cons_NotVaild
                     }
                 }
                 if (iMethod = 1) and not boldInName 
                     return cons_NotVaild
             }
         }
         if Strlen(pObj.if)
         {
             function := pObj.if
             if IsFunc(function)
             {
                 if not %function%()
                     return cons_NotVaild
             }
         }
     }
}

QZ_CheckVaild_Sub(method, keyword, strToNeelde, strMatch)
{
    keyword2 := keyword ";"
    if not method
        return True
    else if (method = 1) and Regexmatch(keyword2, strToNeelde) ;include
        return True
    else if (method = 2) and not Regexmatch(keyword2, strToNeelde) ;exclude
        return True
    else if (method = 3) and regexmatch(strMatch, keyword) ; regex
        return True
    return false
}


QZ_Thread(Terminate=False)
{
    global AhkDllPath
    Static _Thread 
    if not IsObject(_Thread)
        _Thread := AhkDllThread(AhkDllPath)
    if not _Thread.ahkReady() or Terminate
    {
        _Thread.Terminate(1)
        preCode := "#include " A_ScriptDir "\lib\class_json.ahk"
        _Thread.ahktextdll()
        _Thread.ahkExec(preCode)
    }
    return _Thread
}

; 允许其它脚本通过WM_COPYDATA调用
QZ_WM_COPYDATA(wParam, lParam)
{
    global QZ_CMD
    StringAddress := NumGet(lParam + 2*A_PtrSize)  
    ; 获取 CopyDataStruct 的 lpData 成员.
    QZ_CMD := StrGet(StringAddress)  ; 从结构中复制字符串.
    Settimer, QZ_Command_Load, -50 ; 使用settimer运行，方便直接return一个true过去
    return True
}

QZ_Command_Load:
    QZ_Command()
return

QZ_Command()
{
    global QZ_CMD, g_qzConfig, ConfigFile, ObjData
    if RegExMatch(QZ_CMD, "i)config") ; 重新加载命令
    {
        g_qzConfig := Json.Parse(FileRead(configFile))
        ExternalUserEnv()
    }
    if RegExMatch(QZ_CMD, "i)^reload$") ; reload
    {
        reload
    }
    if RegExMatch(QZ_CMD, "i)^/i\s*(.+)", SubLabel) ; reload
    {
        if IsLabel(SubLabel1)
          GoSub, %SubLabel1%
    }
    if RegExMatch(QZ_CMD, "i)^/n(.*)", file) ; reload
    {
        ;msgbox % file
    }
    /*
    if RegExMatch(QZ_CMD, "/i\s*(.+)/o\s*(.+)", SubLabel)
    {
        ObjData := QZ_InitWinOnly()
        ObjData.files := SubLabel2
        if IsLabel(SubLabel1)
          GoSub, %SubLabel1%
    }
    */
}
